﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LostLib.Programs.Properties;
using System.IO;
using System.Data.SqlClient;
using System.Collections;
using System.Net;
using System.Xml;
using System.Globalization;
using System.Reflection;
using System.Diagnostics;

namespace LostLib.Programs
{
	static class CliPackageManager
	{
		static void Search(IEnumerable<string> args)
		{
			string packageName = null;
			string desc = null;

			#region parsing arguments
			var enumerator = args.GetEnumerator();
			while (enumerator.MoveNext())
			{
				switch (enumerator.Current)
				{
				case "-desc":
					if (desc != null) throw new ArgumentException("duplicate -desc");
					desc = enumerator.GetNext();
					break;
				case "-name":
					if (packageName != null) throw new ArgumentException("duplicate -name");
					packageName = enumerator.GetNext();
					break;
				default:
					if (packageName != null) throw new ArgumentException("duplicate -name");
					packageName = enumerator.Current;
					break;
				}
			}
			#endregion

			#region searching
			var packages =
				PackageManager.ApplicationDatabase.Packages
					.Where(package =>
						((packageName == null) || package.PackageName.Contains(packageName))
						&& ((desc == null) || package.Description.Contains(desc)));

			foreach (var package in packages)
			{
				var latest = package.LatestSuitable;
				if (latest == null) continue;
				var installed = package.InstalledVersion;

				if (installed == null) Console.Write("not installed ");
				else if (installed.Version == latest.Version) Console.Write("latest ");
				else Console.Write("old ");

				Console.WriteLine("@{0}", latest.SourceID);
				if (installed != null)
					Console.Write("{0} ", installed);
				Console.WriteLine(latest);
				Console.WriteLine(package.Description);
				Console.WriteLine();
			}
			#endregion
		}

		#region DEBUG
		[Conditional("DEBUG")]
		internal static void WriteDebug(string str)
		{
			Console.Write(str);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0)
		{
			Console.Write(fmt, arg0);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0, object arg1)
		{
			Console.Write(fmt, arg0, arg1);
		}
		[Conditional("DEBUG")]
		internal static void WriteDebug(string fmt, object arg0, object arg1, object arg2)
		{
			Console.Write(fmt, arg0, arg1, arg2);
		}
		#endregion DEBUG

		static int HandleCommand(string[] args)
		{
			switch (args[0])
			{
			case "sources-list":
				SourceList(args.Skip(1));
				break;
			case "help":
				Console.WriteLine("Available commands: ");
				Console.WriteLine("  sources-list - lists all sources");
				Console.WriteLine("  sources-add NAME1 PATH1 [NAME2 PATH2 ...] - adds a source");
				Console.WriteLine("  sources-remove NAME1 [NAME2 ...] - removes specified source");
				Console.WriteLine("  sources-update [NAME1 ...] - updates package list from specified source (or from all sources)");
				Console.WriteLine("  search [-desc] PACKAGE - searches database for package name or description");
				Console.WriteLine("  install [PACKAGE1 ...] - installs specified packages");
				Console.WriteLine("  remove [PACKAGE1 ...] - removes specified packages");
				Console.WriteLine("  config-reconfigure [install|remove] [built-in] - installs/removes db or/and rebuild suitable list");
				break;
			case "sources-add":
				SourceAdd(args.Skip(1));
				break;
			case "sources-remove":
				SourceRemove(args.Skip(1));
				break;
			case "sources-update":
				SourceUpdate(args.Skip(1));
				break;
			case "search":
				Search(args.Skip(1));
				break;
			case "install":
				Install(args.Skip(1));
				break;
			case "remove":
				Remove(args.Skip(1));
				break;
			case "config-reconfigure":
				return ConfigReconfigure(args.Skip(1));
			case "config-show":
				ConfigShow(args.Skip(1));
				break;
			case "":
				if (!PackageManager.Installed)
				{
					ConfigReconfigure(new string[] { "database" });
					return 0;
				}
				break;
			default:
				Console.Error.WriteLine("invalid command {0}", args[0]);
				break;
			}
			return 0;
		}

		static int Main(
				string[] args
			)
		{

			Console.WriteLine("LostLib.Programs v{0}", Assembly.GetExecutingAssembly().GetName().Version);
			if (!PackageManager.Installed)
			{
				ConfigReconfigure(new string[] { "install" });
				if (!PackageManager.Installed) return 0;
			}

			PackageManager.InstallApprover += new EventHandler<ApproveEventArgs<IEnumerable<PackageVersion>>>(PackageManager_InstallApprover);
			PackageManager.RemoveApprover += new EventHandler<ApproveEventArgs<IEnumerable<PackageVersion>>>(PackageManager_RemoveApprover);

			Console.Write("accessing database...");
			foreach (var item in PackageManager.ApplicationDatabase.PackageSources)
				;
			Console.WriteLine("OK");
			if (args.Length == 0)
			{
				Console.Write("#");
				for (string command = Console.ReadLine(); (command != null) && PackageManager.Installed; Console.Write("#"), command = Console.ReadLine())
				{
					args = command.Split(' ');
					if (HandleCommand(args) == 1) return 1;
				}
			} else HandleCommand(args);
			return 0;
		}

		static void PackageManager_RemoveApprover(object sender, ApproveEventArgs<IEnumerable<PackageVersion>> args)
		{
			if (args.Subject.Count() == 0)
			{
				Console.WriteLine("no packages matched");
				args.Approved = true;
			} else
			{
				Console.WriteLine("do you want to remove the following packages:");
				Console.Write("    ");
				foreach (var item in args.Subject)
					Console.Write("{0} ", item);
				Console.WriteLine();
				args.Approved = String.IsNullOrEmpty(Console.ReadLine());
			}
		}

		static void PackageManager_InstallApprover(object sender, ApproveEventArgs<IEnumerable<PackageVersion>> args)
		{
			if (args.Subject.Count() == 0)
			{
				Console.WriteLine("no packages matched");
				args.Approved = true;
			} else
			{
				Console.WriteLine("do you want to install the following packages:");
				Console.Write("    ");
				foreach (var item in args.Subject)
					Console.Write("{0} ", item);
				Console.WriteLine();
				args.Approved = String.IsNullOrEmpty(Console.ReadLine());
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args")]
		private static void ConfigShow(IEnumerable<string> args)
		{
			Console.WriteLine("database configuration");
			Console.WriteLine("connection string: {0}", Settings.Default.ProgramsDB);
			Console.WriteLine();

			Console.WriteLine("built-in configuration");
			Console.WriteLine("built-in path: {0}", Settings.Default.BuiltInSource);
			Console.WriteLine();

			Console.WriteLine("cache configuration");
			Console.WriteLine("cache path: {0}", Settings.Default.PackageCache);
			Console.WriteLine();
		}

		private static int ConfigReconfigure(IEnumerable<string> args)
		{
			foreach (var configSection in args)
			{
				switch (configSection)
				{
				case "install":
					Console.Write("install LostLib.Programs?");
					if (Console.ReadKey().Key != ConsoleKey.Enter)
						break;
					Console.WriteLine();

					PackageManager.CreateDatabase();

					Console.Write("do you want to add demo source?");
					if (Console.ReadKey().Key == ConsoleKey.Enter)
						PackageManager.AddSource("main", "http://194.88.211.202/LostLib/Programs-v0.x/PackagesDB.xml");
					Console.WriteLine();

					Console.Write("update sources now?");
					if (Console.ReadKey().Key == ConsoleKey.Enter)
						SourceUpdate(new string[] { });
					Console.WriteLine();
					break;
				case "remove":
					PackageManager.ApplicationDatabase.DeleteDatabase();
					return 1;
				case "built-in":
					string builtInSource = Settings.Default.BuiltInSource;

					var builtin = PackageManager.ApplicationDatabase.PackageSources.Single(package => package.Name == "built-in");
					builtin.Uri = builtInSource;
					PackageManager.ApplicationDatabase.SubmitChanges();

					PackageManager.UpdateSource("built-in");
					PackageManager.RebuildSuitableVersionList();
					break;
				default:
					throw new NotSupportedException(configSection);
				}
				Settings.Default.Save();
			}
			Settings.Default.Save();
			return 0;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void Install(IEnumerable<string> args)
		{
#if !DEBUG
			try
			{
#endif
				PackageManager.Install(args.Select(arg => PackageVersionFilter.Parse(arg, CultureInfo.CurrentCulture)));
#if !DEBUG
			} catch (Exception cancel)
			{
				Console.WriteLine("ERROR");
				Console.WriteLine("{0}: {1}", cancel.GetType().Name, cancel.Message);
			}
#endif
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void Remove(IEnumerable<string> args)
		{
#if !DEBUG
			try
			{
#endif
				PackageManager.Remove(args
					.Select(arg => PackageVersionFilter.Parse(arg, CultureInfo.CurrentCulture)));
#if !DEBUG
			} catch (Exception cancel)
			{
				Console.WriteLine("ERROR");
				Console.WriteLine("{0}: {1}", cancel.GetType().Name, cancel.Message);
			}
#endif
		}

		private static void SourceUpdate(IEnumerable<string> args)
		{
			var count = 0;
			foreach (var sourceName in args)
			{
				PackageManager.UpdateSource(sourceName);
				count ++;
			}

			if(count == 0)
				foreach (var sourceName in PackageManager.ApplicationDatabase.PackageSources.Select(source => source.Name))
					PackageManager.UpdateSource(sourceName);

			PackageManager.RebuildSuitableVersionList();
		}

		private static void SourceRemove(IEnumerable<string> args)
		{
			foreach (var arg in args)
				PackageManager.RemoveSource(arg);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args")]
		private static void SourceList(IEnumerable<string> args)
		{
			foreach (var source in PackageManager.ApplicationDatabase.PackageSources)
				Console.WriteLine(source);
		}
		private static void SourceAdd(IEnumerable<string> args)
		{
			var iterator = args.GetEnumerator();
   			var name = iterator.GetNext();
			var uri = iterator.GetNext();

			PackageManager.AddSource(name, uri);
		}
	}
}
